package com.globallogic.javatrainings.sergiiivanov.model;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;

import com.globallogic.javatrainings.sergiiivanov.resources.ErrorCodes;

/**
 * Array based generic collection slower than List but uses array as defined in
 * specification<br>
 * See tests.MyArrayTestPerformance
 * 
 * @author Sergey
 * @param <T>
 *            - Class of stored elements
 * 
 * 
 * */

public class MyArray<T> implements Serializable, Iterable<T> {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private Object[] array;

    /**
     * Creates empty collection of provided type <br/>
     * sample:<code>
     * <br/>MyArray&lt;Integer&gt; b = new MyArray&lt;Integer&gt;();</code>
     * 
     */
    public MyArray() {
        array = new Object[0];
    }

    /**
     * Creates collection from provided array of the same type <br/>
     * sample:<code>
     * <br/>Integer[] a = {1,2,3,4,5};
     * <br/>MyArray&lt;Integer&gt; b = new MyArray&lt;Integer&gt;(a);</code>
     * 
     * @param a
     *            - input array
     */
    public MyArray(T[] a) {
        array = a.clone();
    }

    /**
     * adds element into collection
     * 
     * @param element
     *            - new element to add into collection
     * 
     */
    @SuppressWarnings("unchecked")
    public final void add(T element) {
        if (array == null) {
            array = (T[]) Array.newInstance(element.getClass(), 1);
        } else {
            array = Arrays.copyOf(array, size() + 1);
        }
        try {
            set(size() - 1,element);
        } catch (UniversityFailure e) {
            //TODO - this is a crap!!!
        }
    }

    /**
     * Sets all elements of array and array itself into <b>null</b>
     */
    public final void clear() {
        for (int i = 0; i < size(); i++) {
            array[i] = null;
        }
        array = new Object[0];
    }

    /**
     * Returns element stored under index of <b>index</b>
     * 
     * @param index
     *            index of the item in range: 0 .. (size()-1)
     * @return element stored under index of <b>int</b> index
     * @throws UniversityFailure
     *             when <b>index</b> is out of boundaries
     */
    @SuppressWarnings("unchecked")
    public final T get(int index) throws UniversityFailure {
        if (size() > 0) {
            if (0 <= index && index < size()) {
                return (T) array[index];
            }
        }
        throw new UniversityFailure(ErrorCodes.ARRAY_INDEX_IS_OUT_OF_BOUNDARIES);
    }

    /**
     * Lookups provided element in the collection and returns index of first
     * found match<br>
     * Usage samples:<br>
     * <li>if(coll.indexOf(elem)<0){coll.add(elem);} <li>
     * System.out.println("Index of "+s+" = "+coll.indexOf(s));
     * 
     * @param needle
     *            - instance of T
     * @return index of element or -1 if not found
     */
    public final int indexOf(T needle) {
        if (size() > 0) {
            for (int i = 0; i < size(); i++) {
                try {
                    if (get(i).equals(needle)) {
                        return i;
                    }
                } catch (Exception e) {
                    // this should never happen
                    // TODO - check code if collection type changed from array to something else
                }
            }
        }
        return -1;
    }

    /**
     * Removes element under <b>index</b> from the collection and compresses the
     * collection Avoid extensive usage - method is slow yet. See
     * tests.MyArrayTestPerformance
     * 
     * @param index
     *            in collection to remove; range: 0 .. size()-1
     * @throws UniversityFailure
     *             when index is out of boundaries
     */
    public final void remove(int index) throws UniversityFailure {
        if (size() == 0 || index < 0 || index >= size()) {
            throw new UniversityFailure(ErrorCodes.ARRAY_INDEX_IS_OUT_OF_BOUNDARIES);
        }
        if (index == 0) {
            array = Arrays.copyOfRange(array, 1, size());
        } else if (index == size() - 1) {
            array = Arrays.copyOfRange(array, 0, size() - 1);
        } else if (size() - index > index) {
            // shift to end
            for (int i = index; i > 0; i--) {
                array[i] = array[i - 1];
            }
            array = Arrays.copyOfRange(array, 1, size());
        } else {
            // shift to start
            for (int i = index; i < size() - 2; i++) {
                array[i] = array[i + 1];
            }
            array = Arrays.copyOfRange(array, 0, size() - 1);
        }
    }

    /**
     * Removes element under index and shrinks collection
     * 
     * @param o
     *            - element of T to remove
     * @return <li>true, if element removed <li>false, if element not found
     * @throws UniversityFailure
     *             when code is broken
     */
    public final boolean remove(T o) {
        int f = indexOf(o);
        if (f >= 0) {
            try {
                remove(f);
            } catch (UniversityFailure e) {
                //TODO crap, how come?
            }
            return true;
        }
        return false;
    }

    /**
     * Replaces element value under <b>index</b> with provided <b>element</b> of
     * the same type
     * 
     * @param index
     *            in collection to replace; range: 0 .. (size()-1)
     * @param element
     *            to replace to
     * @throws UniversityFailure
     *             when index is out of boundaries
     */
    public final void set(int index, T element) throws UniversityFailure {
        if (size() > 0) {
            if (index >= 0 && index < size()) {
                array[index] = element;
                return;
            }
        }
        throw new UniversityFailure(ErrorCodes.ARRAY_INDEX_IS_OUT_OF_BOUNDARIES);
    }

    /**
     * Returns elements count
     * 
     * @return number of stored elements
     */
    public final int size() {
        if (array == null) {
            return 0;
        }
        return array.length;
    }

    /**
     * Converts collections into the array <br/>
     * WARNING! check type of result from empty collection (size()==0) <br/>
     * when size()==0, result is Object[]
     * 
     * @return <b>array</b> of elements in the collection or <b>null</b> when no
     *         elements in collection
     */
    @SuppressWarnings("unchecked")
    public T[] toArray() {
        return (T[]) array.clone();
    }

    /**
     * Returns names of stored elements that implement
     * {@link MyArrayToNamesCovertable} interface<br>
     * Calls element.getName() for each element
     * 
     * @return String[]
     * @throws UniversityFailure
     *             when collection does not implement
     *             {@link MyArrayToNamesCovertable}
     */
    public String[] toNamesList() throws UniversityFailure {
        String[] result = new String[size()];
        for (int i = 0; i < size(); i++) {
            if (array[i] instanceof MyArrayToNamesCovertable) {
                MyArrayToNamesCovertable element = (MyArrayToNamesCovertable) array[i];
                result[i] = element.getName();
            } else {
                throw new UniversityFailure(ErrorCodes.ELEMENT_TYPE_NOT_SUPPORT_NAMES_CONVERSION);
            }
        }
        return result;
    }

    /**
     * Allows to iterate collection.<br>
     * Safer to use than for loop through elements with get(i)
     */
    @Override
    public Iterator<T> iterator() {
        Iterator<T> iterator = new Iterator<T>() {
            private int currentIndex = 0;

            @Override
            public boolean hasNext() {
                return currentIndex < size();
            }

            @SuppressWarnings("unchecked")
            @Override
            public T next() {
                return (T) array[currentIndex++];
            }

            @Override
            public void remove() {}
        };
        return iterator;
    }

    /**
     * Returns new copy of stored collection.<br>
     * Calls {@link #toArray()} for making new {@link MyArray}<br>
     * Identical to MyArray&lt;T&gt; newArr = new
     * MyArray&lt;T&gt;(oldArr.toArray());
     * 
     * @return {@link MyArray} as a copy of collection
     * @see #MyArray(Object[])
     */
    public MyArray<T> copy() {
        return new MyArray<T>(toArray());
    }
}
